Skip to content

[trainer, worker, fsdp, cfg, tests] feat: add multi-role distillation runtime - #546

Open
NancyFyong wants to merge 9 commits into
verl-project:distillation-accelerationfrom
NancyFyong:distillation-pr2-runtime
Open

[trainer, worker, fsdp, cfg, tests] feat: add multi-role distillation runtime#546
NancyFyong wants to merge 9 commits into
verl-project:distillation-accelerationfrom
NancyFyong:distillation-pr2-runtime

Conversation

@NancyFyong

@NancyFyong NancyFyong commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

PR 2 of RFC #519: distributed role execution, independent student/fake-score optimizers and schedulers, LoRA/EMA, and composite checkpoint/resume. Runtime specification: #535.

Stacked on distillation-acceleration / #545; review the incremental diff against that parent. This is not a runnable model recipe: a separate architecture adapter must supply conditioning, forwards, score conversion, and differentiable student computation.

Checklist Before Starting

This is #519's runtime layer, not a second controller or OPD trainer. It preserves OPD (#293, #495) and reuses existing trainer/FSDP foundations, adding independent fake-score optimization and multi-role state management.

Test

Local results at 28e2943, based on PR 1 1ef230a, using Python 3.12.13 / PyTorch 2.13.0+cu129 in the verlomni environment:

Check Result
Full repository L1 CPU suite 1,441 passed
Named-adapter and merged-LoRA export tests, run separately 14 passed
Config documentation check 1 passed
Generated configs and pre-commit over the complete incremental PR file set Passed

CPU coverage includes dispatch/futures, typed configs, sample/element accumulation, gradient ownership, finite-step accounting, EMA/export, and checkpoint fingerprints, using fake workers where appropriate.

Limits: historical one-rank FSDP1/FSDP2 evidence in #535 was not rerun for this head. The denominator collective still needs real multi-rank validation; no current-head production-training or inference-parity claim is made. Remote CPU CI only targets main / v0.* bases, so this stacked PR's CPU results are local.

Validation commands

Run from the PR checkout with the project environment active:

export PYTHONPATH=. OMP_NUM_THREADS=2 TORCH_COMPILE_DISABLE=1 TORCHINDUCTOR_DISABLE=1
CUDA_VISIBLE_DEVICES='' python -m pytest -c /dev/null \
  -o 'python_files=*_on_cpu.py' -p no:cacheprovider --no-cov --asyncio-mode=auto -q tests/
CUDA_VISIBLE_DEVICES='' python -m pytest -c /dev/null \
  -p no:cacheprovider --no-cov --asyncio-mode=auto -q \
  tests/workers/test_diffusion_distillation_lora_on_cpu.py \
  tests/workers/test_diffusers_fsdp_merged_lora_on_cpu.py
CUDA_VISIBLE_DEVICES='' python -m pytest -c /dev/null \
  -p no:cacheprovider --no-cov --asyncio-mode=auto -q tests/special_sanity/test_config_docs.py
bash scripts/generate_trainer_config.sh
BASE=1ef230a880ee67ce8a45fc501e44b39904360e6d
mapfile -d '' files < <(git diff --cached --name-only --diff-filter=ACMR -z "$BASE")
python -m pre_commit run --files "${files[@]}"

--files avoids a local Git incompatibility with --all-files.

API and Usage Example

Configuration fragment for a separately registered architecture adapter, not a standalone launch recipe:

algorithm:
  trainer_type: distillation
  sample_source: offline

actor_rollout_ref:
  actor:
    strategy: fsdp2
    optim:
      lr: 0.0001
      lr_scheduler_type: constant
  model:
    algorithm: dmd2
    lora_rank: 32

distillation:
  enabled: false
  distribution_matching:
    recipe: dmd2
    profile: distribution_only
    role_storage: shared_base_adapters
    fake_update_ratio: 2
    student_micro_batch_size_per_gpu: 1
    fake_score_micro_batch_size_per_gpu: 1
    fake_score_optim:
      lr: 0.00002
      lr_scheduler_type: constant
    ema_decay: 0.999
    ema_start_step: 0
    export_role: student

offline bypasses external RL rollout; student computation stays inside training. distillation.enabled remains OPD-only. Exporting student leaves EMA in resumable state.

The opt-in DistributionMatchingModelAdapter.build_distribution_matching_computer(model_config, plan) returns a DistributionMatchingComputer with:

  • compute_phase(request, batch, runtime)DistillationPhaseComputation: one graph-bearing scalar loss for the requested role plus detached metrics;
  • state_dict() / load_state_dict(state): checkpointable sampling/RNG state.

The computer owns model math; the runtime owns optimization. Existing DiffusionModelBase adapters gain no new mandatory abstract methods.

Design & Code Changes

main_diffusion / TaskRunner
  -> DistillationRayTrainer(BaseRayDiffusionTrainer)
     -> PR 1 DistillationTrainerController
        -> DiffusionDistillationWorkerGroup (executor facade)
           -> Ray worker / DistributionMatchingComputer
              -> DistillationRoleRuntime
                 -> one DistillationRoleGroupEngine per physical group
                    -> existing DiffusersFSDPEngine

Ownership and reuse

Source Responsibility
trainer/diffusion/distillation/ray_trainer.py Existing trainer lifecycle, stateful batches, role scheduler horizons, tracking and completed-cycle checkpoints
workers/diffusion_distillation_worker.py Executor facade, Ray worker, computation protocol, role routing and micro-batch accumulation
workers/engine/fsdp/distillation_impl.py Physical FSDP module, disjoint role parameters, optimizers/schedulers, role contexts and group state
pipelines/model_base.py Opt-in architecture capability and computer-construction seam
Existing Diffusers engine and LoRA mixin Reused loading/FSDP/checkpoint primitives; selected-adapter export metadata and context restoration

Paths are relative to verl_omni/. The worker reuses verl dispatch/profiling and EngineRegistry, not a new loading backend or PPO-shaped execution.

Roles vs. storage: shared_base_adapters uses one frozen base with student/fake-score/EMA adapters and an adapter-disabled teacher. colocated_independent uses separate physical groups as the full-module fallback/correctness baseline. Sharing is not an algorithm requirement. Both layouts are colocated; shared storage requires LoRA, and shared FSDP1 requires use_orig_params=true.

One optimizer owner per phase: accumulate micro-batches, reject inactive-role gradients, normalize/clip, then step. Scheduler and eligible EMA updates require a finite successful step. Student/fake micro-batches and scheduler horizons are independent; PPO batch fields are not repurposed.

With loss_normalizer=None, accumulation uses sample means. An explicit positive count accumulates numerator gradients and divides by the DP-averaged denominator before clipping. Invalid/mixed modes fail closed; this does not imply globally element-weighted aggregation for every metric.

Architecture boundary: during a student phase, no-grad teacher/fake scoring must preserve the pending student graph; the score branches are detached. Role contexts restore adapter and train/eval state. Phase RPCs are blocking; computers still own synchronization of collective-dependent forward counts and branches.

Checkpoint vs. export: completed-cycle checkpoints atomically publish role groups, optimizers/schedulers, EMA, computer/driver RNG, dataloader and controller state. Persisted models are saved once per physical group; frozen teacher-only groups reload their base. Canonical fingerprints reject plan/config/layout drift. Publication is atomic; failed updates/restores are not in-memory rollback transactions.

Export selects student or student_ema with its own PEFT config and restores the previous adapter. Upstream merged-default export remains intact; named adapters are not silently substituted. This does not implement CheckpointEngine transfers or validation replicas.

Not included: architecture-specific training/generation, adversarial multi-optimizer phases, causal/KV-cache execution, standalone score transport, inference orchestration, HDFS restore, or automatic checkpoint retention. Model integration begins with #543; later RFC stages supply the remaining capabilities.

Checklist Before Submitting

AI assistance: Claude and OpenAI via pi assisted implementation, tests, and review. The human submitter previously confirmed line-by-line review and testing of the earlier changes; the latest delta still needs final review.

NancyFyong and others added 5 commits September 6, 2026 10:31
… runtime

Bind the generic distillation control plane to colocated FSDP role groups
with independent optimizer state, named-LoRA role switching, EMA,
profiling metrics, and atomic composite checkpoint/resume. Preserve an
independent-module correctness path and fail closed at deferred
architecture and adversarial boundaries.

Refs: verl-project#519

AI assistance (OpenAI Codex) was used for this change.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: NancyFyong <2742092809@qq.com>
Match the established worker profiler setup so nested tool settings are
converted from OmegaConf before DistProfiler construction. This prevents
production distillation workers from failing during actor initialization.

AI assistance (OpenAI Codex) was used for this change.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: NancyFyong <2742092809@qq.com>
Resolve DataProtoFuture values returned by nonblocking worker dispatch before
the driver converts phase metrics and optimizer counters. This lets the real
Ray data plane complete a distillation cycle.

AI assistance (OpenAI Codex) was used for this change.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: NancyFyong <2742092809@qq.com>
…stabilize resume

Resolve phase RPC failures before collecting lazy rank metadata, construct
only the actor subconfigs needed by distillation, and fingerprint plans
through canonical JSON rather than order-dependent repr strings.

Keep newly introduced helpers flat and descriptively named. Inspect fake
adapters directly in isolation tests instead of using inference export.

Validation: 449 CPU regressions, both one-rank FSDP role/checkpoint tests,
generated-config verification, and all pre-commit hooks passed.
Refs verl-project#535

AI assistance (OpenAI via pi) was used for this change.

Co-authored-by: OpenAI <noreply@openai.com>
Signed-off-by: NancyFyong <2742092809@qq.com>
Carry the PR 1 controller naming through checkpoint state handling and tests after rebasing the multi-role runtime. Keep framework-defined private hooks unchanged.

AI assistance (OpenAI via pi) was used for this change.

Co-authored-by: OpenAI <noreply@openai.com>

Signed-off-by: NancyFyong <2742092809@qq.com>
@NancyFyong
NancyFyong force-pushed the distillation-pr2-runtime branch from 80d7b92 to ddbcd4e Compare September 6, 2026 03:15
@github-actions github-actions Bot removed the ci-core Run all core modules - training, rollout, reward engines, weight sync manager, etc label Sep 6, 2026
@NancyFyong NancyFyong added the ci-core Run all core modules - training, rollout, reward engines, weight sync manager, etc label Sep 6, 2026
…utionMatchingComputer

The architecture-owned differentiable computation object misused the *Runner
suffix, which in this repo denotes a top-level task entrypoint (TaskRunner,
RayTrainerTaskRunner). Rename it to match the algorithm family it belongs to
and the DistributionMatchingModelAdapter that builds it:

  DistillationPhaseRunner        -> DistributionMatchingComputer   (Protocol)
  build_distillation_phase_runner-> build_distribution_matching_computer  (hook)
  self.phase_runner              -> self.dm_computer               (worker attr)
  phase_runner_rank_*.pt         -> dm_computer_rank_*.pt          (per-rank state)

The control-plane phase contracts (PhaseRequest, PhaseResult, UpdatePhaseSpec,
DistillationPhaseExecutor) and the compute_phase method are deliberately kept,
since a cycle is genuinely modelled as student/fake phases. Renaming the
per-rank state file changes resume compatibility for pre-existing checkpoints.

Validated in the required environment: 449 focused trainer/worker CPU tests
pass; ruff, mypy, generated-config verification and all staged pre-commit hooks
pass.

Co-authored-by: OpenAI Codex
Signed-off-by: NancyFyong <2742092809@qq.com>
@github-actions github-actions Bot removed the ci-core Run all core modules - training, rollout, reward engines, weight sync manager, etc label Sep 6, 2026
…-batches

Allow architecture computers to declare an explicit loss denominator.
Accumulate numerator gradients and normalize over data-parallel counts
before clipping and stepping; preserve sample means by default.

Validation: 470 targeted CPU tests passed; applicable pre-commit
hooks, config regeneration, and diff checks passed.

AI assistance (pi coding agent) was used for this change.

Co-authored-by: pi coding agent
Signed-off-by: NancyFyong <2742092809@qq.com>
Merge distillation-acceleration without rewriting published history.
The resulting tree is identical to the CPU-tested repair snapshot.

AI assistance (pi coding agent) was used for this change.

Co-authored-by: pi coding agent
Signed-off-by: NancyFyong <2742092809@qq.com>
Merge the updated PR1 baseline without dropping main's merged-weight
streaming path or the role-aware adapter selection and PEFT metadata.
Keep CPU export fixtures independent of GPU memory probes and exercise
implicit default, explicit default, student, and student EMA adapters.

Validation: 1,441 L1 CPU tests, 14 focused LoRA export tests, and the
config-doc test pass. Generated configs are stable and all changed-file
pre-commit hooks pass.

AI assistance (ChatGPT via Pi) was used for this change.

Co-authored-by: ChatGPT (via Pi)
Signed-off-by: NancyFyong <2742092809@qq.com>
@NancyFyong NancyFyong added ci-core Run all core modules - training, rollout, reward engines, weight sync manager, etc and removed ci-core Run all core modules - training, rollout, reward engines, weight sync manager, etc labels Sep 8, 2026
@NancyFyong
NancyFyong marked this pull request as ready for review September 8, 2026 06:05
@NancyFyong

Copy link
Copy Markdown
Collaborator Author

Proposal: share the existing OPD configuration, keep the DMD execution flow

For #545 and this PR (RFC #519 / #535), would it make sense to consolidate further around the existing DiffusionDistillationConfig and DiffusionDistillationTeacherModelConfig, rather than maintain separate sources of teacher configuration?

At 28e2943, the configs already share the same top-level container, but DMD still derives teacher role paths from the student model and treats distillation.enabled as OPD-only. The proposed change is actual configuration reuse, not just placing the fields in the same file.

Suggested boundary:

  • Use distillation.teacher_models as the shared source of teacher identities/checkpoints, reusing teacher parsing/validation and compatible model-loading/resource-pool helpers. Resolve these settings into DMD's internal role/group plan rather than asking users to configure the same teacher twice.
  • Select execution through algorithm.trainer_type: preserve the existing policy-gradient + OPD path, while distillation uses the DMD controller and multi-role runtime. Accepting enabled=true for DMD requires separating OPD-specific activation guards in the entrypoint/base trainer, not merely removing a validation error.
  • Keep using existing actor optimizer/FSDP, trainer and data settings. Retain only DMD-specific options—fake-score optimization, update ratios, role storage and EMA—under distribution_matching.

Illustrative target configuration, not supported by the current head:

algorithm:
  trainer_type: distillation
  sample_source: offline

distillation:
  enabled: true
  nnodes: 0
  teacher_models:
    teacher_model:
      model_path: ${actor_rollout_ref.model.path}
  distribution_matching:
    recipe: dmd2
    fake_update_ratio: 2
    role_storage: shared_base_adapters

Sharing configuration would not mean sending DMD through the PPO update loop or treating OPD's teacher_prev_sample_mean as DMD's arbitrary-sigma score/x0. Those computation contracts remain distinct.

For a bounded first change, I suggest a single colocated teacher. Roles may share a physical base only when their checkpoint/configuration identities are compatible; a different teacher must occupy an explicitly supported separate group, not silently use the student's base. Unsupported DMD multi-teacher routing, standalone teacher pools and OPD one_step_off scheduling should fail before allocation, not be silently ignored.

Compatibility tests should preserve existing OPD behavior, explicitly handle legacy DMD configs with enabled=false, and prove that changing the shared teacher checkpoint changes the teacher DMD actually loads and records in its plan/checkpoint identity.

Would you support making this consolidation in #545/#546 before merge? In particular, does sharing the teacher/configuration infrastructure while retaining the DMD-specific execution flow and subconfiguration look like the right boundary? No implementation changes have been made for this proposal yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant